﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
using System.Windows.Markup;

namespace Creventive.Wpf.ShellFactory.Debugging
{
    /// <summary>
    /// Prints debug output on bindings. Use as a converter.
    /// </summary>
    public class Output : MarkupExtension, IValueConverter
    {
        #region Statics

        private const string Null = "{x:Null}";

        #endregion

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Output" /> class.
        /// </summary>
        public Output()
        {}

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Output" /> class.
        /// </summary>
        /// <param name = "innerConverter">The inner converter.</param>
        public Output(IValueConverter innerConverter)
        {
            this.InnerConverter = innerConverter;
        }

        /// <summary>
        ///   Gets or sets the converter to invoke inside this debug converter.
        /// </summary>
        /// <value>The inner converter.</value>
        [ConstructorArgument("innerConverter")]
        public IValueConverter InnerConverter { get; set; }

        /// <summary>
        ///   Gets or sets a short single-line description text to display with the debug output
        /// </summary>
        /// <value>The text.</value>
        public string Text { get; set; }

        /// <summary>
        /// When implemented in a derived class, returns an object that is set as the value of the target property for this markup extension.
        /// </summary>
        /// <param name="serviceProvider">Object that can provide services for the markup extension.</param>
        /// <returns>
        /// The object value to set on the property where the extension is applied.
        /// </returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider != null)
            {
                var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                if (target != null)
                {
                    var targetObject = target.TargetObject;
                    var targetProperty = target.TargetProperty;
                    if (targetObject != null)
                        Debug.WriteLine("Target: {0}", (object)targetObject.GetType().FullName);
                    if (targetProperty is PropertyInfo)
                    {
                        var propertyInfo = (PropertyInfo)targetProperty;
                        Debug.WriteLine("Target Property: {0} {1}", propertyInfo.PropertyType.FullName, propertyInfo.Name);
                    }
                    else
                        Debug.WriteLine("Target Property: ???");
                }
            }
            return this;
        }

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            IValueConverter innerConverter = this.InnerConverter;
            this.Dump("Convert", this.Text, value, targetType, parameter, culture);
            if (innerConverter != null)
            {
                return this.DumpConversion(innerConverter,
                                           c => c.Convert(value, targetType, parameter, culture));
            }
            else
                return value;
        }

        private void Dump(string command, string text, object value, Type targetType, object parameter, CultureInfo culture)
        {
            string valueText;
            string parameterText;
            string cultureText;
            string targetTypeText;
            if (value == null)
                valueText = Null;
            else
                valueText = value.ToString();
            if (targetType == null)
                targetTypeText = Null;
            else
                targetTypeText = targetType.FullName;
            if (parameter == null)
                parameterText = Null;
            else
                parameterText = parameter.ToString();
            if (culture == null)
                cultureText = Null;
            else
                cultureText = culture.Name;
            if (string.IsNullOrWhiteSpace(text))
                Debug.WriteLine("{0}:", (object)command);
            else
                Debug.WriteLine("{0} ({1}):", command, text);
            Debug.WriteLine("  targetType={0}", (object)targetTypeText);
            Debug.WriteLine("  parameter={0}", (object)parameterText);
            Debug.WriteLine("  culture={0}", (object)cultureText);
            Debug.WriteLine("  value={0}", (object)valueText);
        }

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value that is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            IValueConverter innerConverter = this.InnerConverter;
            this.Dump("ConvertBack", this.Text, value, targetType, parameter, culture);
            if (innerConverter != null)
            {
                return this.DumpConversion(innerConverter,
                                           c => c.ConvertBack(value, targetType, parameter, culture));
            }
            else
                return value;
        }

        private object DumpConversion(IValueConverter innerConverter, Func<IValueConverter, object> convert)
        {
            Debug.WriteLine("  Converting with {0}:", innerConverter.GetType().FullName);
            try
            {
                string valueText;
                var value = convert(innerConverter);
                if (value == null)
                    valueText = Null;
                else
                    valueText = value.ToString();
                Debug.WriteLine("    newValue={0}", (object)valueText);
                return value;
            }
            catch (Exception e)
            {
                string[] segments = e.ToString().Split(new[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.None);
                string message = string.Join(Environment.NewLine + "          ", segments);
                Debug.WriteLine("    value={0}", (object)message);
                return null;
            }
        }
    }
}